package abacab;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import abacab.actor.ActorDef;
import abacab.exec.MessageExecutorHandler;
import abacab.ref.ActorRef;
import abacab.ref.LocalActorRef;

import com.typesafe.config.Config;

/**
 * The instance for the actor system
 */
@SuppressWarnings("rawtypes")
public class ActorSystemImpl extends ActorSystem {
	/** The factory for message executors */
	private final MessageExecutorHandler	messageExecutorFactory;

	/** The root actors */
	private final Map<String, ActorRef>		actors;

	/** Indicates that the system is stopped */
	private final AtomicBoolean						stopped;

	/**
	 * Create the system
	 * 
	 * @param name
	 *          the system's name
	 * @param config
	 *          the configuration
	 */
	ActorSystemImpl(String name, Config config) {
		super(name, config);
		this.messageExecutorFactory = new MessageExecutorHandler();
		this.actors = new HashMap<String, ActorRef>();
		this.stopped = new AtomicBoolean(false);
	}

	/**
	 * @return the factory for message executors
	 */
	public MessageExecutorHandler getMessageExecutorFactory() {
		return this.messageExecutorFactory;
	}

	@Override
	public void shutdown() {
		if (!this.stopped.getAndSet(true)) {
			synchronized (this.actors) {
				for (ActorRef ref : this.actors.values()) {
					ref.stop();
				}
				this.actors.clear();
			}
			this.messageExecutorFactory.stop();
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public <M> ActorRef<M> createActor(String reference, ActorDef<M> definition) {
		ActorRef<M> actorRef;
		synchronized (this.actors) {
			actorRef = this.actors.get(reference);
			if (actorRef != null) {
				throw new IllegalStateException("Actor " + reference + " already exists");
			}
			actorRef = new LocalActorRef<M>(reference, this, definition);
			this.actors.put(reference, actorRef);
		}
		return actorRef;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <M> ActorRef<M> getActor(String reference) {
		synchronized (this.actors) {
			return this.actors.get(reference);
		}
	}
}